package com.adulcimer.utils;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.http.HttpUtil;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.adulcimer.core.config.MinioConfig;
import com.adulcimer.core.tools.DemonExceptionMessage;
import com.adulcimer.models.constant.ExceptionConstant;
import com.adulcimer.models.dto.commen.UploadFileDto;
import com.adulcimer.models.vo.commen.UploadFileVm;
import io.minio.*;
import io.minio.errors.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.util.IOUtils;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import ws.schild.jave.info.MultimediaInfo;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;

/**
 * @author Simon
 * @create 2025/8/20
 * @Description 关于minio的相关工具类
 **/

@Component
@Slf4j
public class MinioUtils {

    private final MinioClient minioClient;
    private final String endpoint;
    private final String bucketName;

    private static final String PATH_SEPARATOR = "/";

    private static final Map<String, String> MIME_TYPE_MAP = new HashMap<>();

    private static final String DATE_FORMAT = "yyyyMMdd";

    @Autowired
    public MinioUtils(MinioClient minioClient, MinioConfig minioConfig) {
        this.minioClient = minioClient;
        this.endpoint = minioConfig.getEndpoint();
        this.bucketName = minioConfig.getBucketName();
    }

    static {
        // 图片
        MIME_TYPE_MAP.put("jpg", "image/jpeg");
        MIME_TYPE_MAP.put("jpeg", "image/jpeg");
        MIME_TYPE_MAP.put("png", "image/png");
        MIME_TYPE_MAP.put("gif", "image/gif");

        // 视频
        MIME_TYPE_MAP.put("mp4", "video/mp4");
        MIME_TYPE_MAP.put("avi", "video/x-msvideo");
        MIME_TYPE_MAP.put("wmv", "video/x-ms-wmv");
        MIME_TYPE_MAP.put("mpg", "video/mpeg");
        MIME_TYPE_MAP.put("mov", "video/quicktime");
        MIME_TYPE_MAP.put("rm", "application/vnd.rn-realmedia");
        MIME_TYPE_MAP.put("swf", "application/x-shockwave-flash");
        MIME_TYPE_MAP.put("flv", "video/x-flv");
        MIME_TYPE_MAP.put("mkv", "video/x-matroska");

        // 图标
        MIME_TYPE_MAP.put("ico", "image/x-icon");

        // 压缩文件
        MIME_TYPE_MAP.put("zip", "application/zip");
        MIME_TYPE_MAP.put("7z", "application/x-7z-compressed");
        MIME_TYPE_MAP.put("tar", "application/x-tar");
        MIME_TYPE_MAP.put("gz", "application/gzip");
        MIME_TYPE_MAP.put("bz2", "application/x-bzip2");

        // 文本
        MIME_TYPE_MAP.put("pdf", "application/pdf");
        MIME_TYPE_MAP.put("txt", "text/plain");
        MIME_TYPE_MAP.put("docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
        MIME_TYPE_MAP.put("doc", "application/msword");
        MIME_TYPE_MAP.put("xls", "application/vnd.ms-excel");
        MIME_TYPE_MAP.put("xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
    }

    /**
     * 创建bucket
     */
    public void createBucket(String bucketName) {
        try {
            if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {
                minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
                // 配置存储同策略为Public
                minioClient.setBucketPolicy(SetBucketPolicyArgs.
                        builder().
                        bucket(bucketName).
                        config("{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"PublicReadGetObject\",\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"*\"},\"Action\":[\"s3:GetObject\"],\"Resource\":[\"arn:aws:s3:::" + bucketName + "/*\"]}]}").
                        build());
            }
        } catch (Exception e) {
            throw new DemonExceptionMessage(e.getMessage());
        }
    }

    public UploadFileVm uploadByUrl(UploadFileDto uploadFileDto) {
        try {
            String replace = UUID.randomUUID().toString().replace("-", "");
            String fileName = FileUtil.getTmpDirPath() + PATH_SEPARATOR + replace + uploadFileDto.getName();
            OutputStream outputStream = new FileOutputStream(fileName);
            HttpUtil.download(uploadFileDto.getUrl(), outputStream, true);
            // 将OutputStream转为InputStream
            InputStream inputStream = new FileInputStream(fileName);
            UploadFileVm upload = upload(inputStream, uploadFileDto.getName());
            // 删除临时文件
            FileUtil.del(fileName);
            return upload;
        } catch (FileNotFoundException e) {
            throw new DemonExceptionMessage(e.getMessage());
        }

    }

    public Boolean removeFiles(List<String> fileUrls) {
        AtomicReference<Boolean> flag = new AtomicReference<>(true);
        fileUrls.forEach(url -> {
            try {
                url = url.replace(endpoint + PATH_SEPARATOR + bucketName + PATH_SEPARATOR, "");
                minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(url).build());
            } catch (Exception e) {
                log.error("删除文件失败", e);
                flag.set(false);
            }
        });
        return flag.get();
    }

    /**
     * 获取全部bucket
     *
     * @return
     */
    public List<Bucket> getAllBuckets() {
        try {
            return minioClient.listBuckets();
        } catch (Exception e) {
            throw new DemonExceptionMessage(e.getMessage());
        }
    }

    private static String getFileSha256(InputStream inputStream) {
        // 计算文件的md5,避免文件重复保存
        String sha256Str = "";
        try {
            MessageDigest sha256 = MessageDigest.getInstance("MD5");
            byte[] buffer = new byte[8192];
            int read;
            while ((read = inputStream.read(buffer)) != -1) {
                sha256.update(buffer, 0, read);
            }
            byte[] sha256bytes = sha256.digest();
            StringBuilder hexString = new StringBuilder();
            for (byte b : sha256bytes) {
                String hex = Integer.toHexString(0xff & b);
                if (hex.length() == 1) {
                    hexString.append('0');
                }
                hexString.append(hex);
            }
            // 计算的出
            sha256Str = hexString.toString();
            return sha256Str;
        } catch (NoSuchAlgorithmException | IOException e) {
            log.error(ExceptionConstant.UPLOAD_FILE_FAILURE, e);
            throw new DemonExceptionMessage(ExceptionConstant.UPLOAD_FILE_FAILURE);
        }
    }

    /**
     * description: 上传文件
     *
     * @param multipartFile
     * @return: java.lang.String
     */
    public UploadFileVm upload(MultipartFile multipartFile) {
        UploadFileVm uploadFileVm = new UploadFileVm();
        String sha256Str = "";
        try {
            sha256Str = getFileSha256(multipartFile.getInputStream());
        } catch (IOException e) {
            throw new DemonExceptionMessage(e.getMessage());
        }
        String url;
        //这块可能有浏览器不支持getOriginalFilename(注意)
        String fileName = multipartFile.getOriginalFilename();
        if (StringUtils.isNotBlank(fileName)) {
            fileName = fileName.replace(" ", "");
        }
        String originalFileName = multipartFile.getOriginalFilename();
        //获取文件对应扩展名
        String extName = FileNameUtil.extName(fileName);
        //根据扩展名获取对应ContentType 路径，再拼装对应文件名
        //需要修改fileName +
        String getContentType = getContentType(fileName);
        extName = "." + extName;
        if (StringUtils.isEmpty(sha256Str)) {
            sha256Str = DateUtil.format(new Date(), DATE_FORMAT) + PATH_SEPARATOR + UUID.randomUUID().toString().replace("-", "");
        }
        fileName = "upload/" + getContentType + PATH_SEPARATOR + sha256Str + PATH_SEPARATOR + fileName;
        url = endpoint + PATH_SEPARATOR + bucketName + PATH_SEPARATOR + fileName;
        // 去除url空格
        uploadFileVm.setFileUrl(url);
        uploadFileVm.setSize(multipartFile.getSize());
        uploadFileVm.setExt(extName);
        uploadFileVm.setName(originalFileName);
        uploadFileVm.setContentType(getContentType);
        // 判断是否是图片类型
        try (InputStream in = multipartFile.getInputStream()) {

            minioClient.putObject(PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(fileName)
                    .stream(in, in.available(), -1)
                    .contentType(multipartFile.getContentType())
                    .build());
            if (isImageFile(extName)) {
                BufferedImage read = ImageIO.read(multipartFile.getInputStream());
                uploadFileVm.setHeight(read.getHeight() + "");
                uploadFileVm.setWidth(read.getWidth() + "");
            }
            // 判断如果是音频文件获取音频时常
            if (isAudioFile(extName)) {
                File tempFile = FileUtil.createTempFile(UUID.randomUUID().toString(), true);
                multipartFile.transferTo(tempFile);
                double audioDuration = getAudioDuration(tempFile);
                uploadFileVm.setDuration(audioDuration + "");
            }
        } catch (Exception e) {
            log.error(ExceptionConstant.UPLOAD_FILE_FAILURE, e);
            throw new DemonExceptionMessage(e.getMessage());
        }
        return uploadFileVm;
    }

    // 批量上传
    public List<UploadFileVm> upload(List<MultipartFile> multipartFiles) {
        return multipartFiles.stream().map(this::upload).toList();
    }

    private static boolean isAudioFile(String fileExt) {
        String[] audioExtensions = {"wav", "flac", "aac", "mp4", "aiff", "mp3", "wma", "avi"};
        for (String ext : audioExtensions) {
            if (fileExt.endsWith(ext)) {
                return true;
            }
        }
        return false;
    }

    private static boolean isImageFile(String fileExt) {
        String[] audioExtensions = {"jpg", "png", "gif", "bmp", "jpeg", "webp", "svg", "tiff", "ico"};
        for (String ext : audioExtensions) {
            if (fileExt.endsWith(ext)) {
                return true;
            }
        }
        return false;
    }

    private static double getAudioDuration(File mediaFile) {
        FfmpegFileInfo ffmpegFileInfo = new FfmpegFileInfo(mediaFile);
        MultimediaInfo multimediaInfo;
        multimediaInfo = ffmpegFileInfo.getInfo(mediaFile.getPath());
        return multimediaInfo.getDuration() / 1000.0;
    }

    public UploadFileVm upload(InputStream inputStream, String userId, String fileName, int size) {
        // 深拷贝inputStream
        String fileSha256;
        byte[] byteArray;
        try {
            byteArray = IOUtils.toByteArray(inputStream);
            inputStream = new ByteArrayInputStream(byteArray);
            fileSha256 = getFileSha256(inputStream);
        } catch (IOException e) {
            throw new DemonExceptionMessage(e.getMessage());
        }
        UploadFileVm uploadFileVm = new UploadFileVm();
        String uploadPath = "convertFile/" + userId + PATH_SEPARATOR + fileSha256 + PATH_SEPARATOR + fileName;
        String url = endpoint + PATH_SEPARATOR + bucketName + PATH_SEPARATOR + uploadPath;
        uploadFileVm.setFileUrl(url);
        uploadFileVm.setSize(size);
        uploadFileVm.setExt(FileNameUtil.extName(fileName));
        uploadFileVm.setName(fileName);
        try(InputStream in = new ByteArrayInputStream(byteArray)) {
            minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(bucketName)
                            .object(uploadPath)
                            .stream(in, size, -1)
                            .build());
            return uploadFileVm;
        } catch (ErrorResponseException e) {
            log.error("错误响应异常", e);
            throw new DemonExceptionMessage(ExceptionConstant.UPLOAD_FILE_FAILURE);
        } catch (InsufficientDataException e) {
            log.error("数据不足异常", e);
            throw new DemonExceptionMessage(ExceptionConstant.UPLOAD_FILE_FAILURE);
        } catch (InternalException e) {
            log.error("内部异常", e);
            throw new DemonExceptionMessage(ExceptionConstant.UPLOAD_FILE_FAILURE);
        } catch (InvalidKeyException e) {
            log.error("无效密钥异常", e);
            throw new DemonExceptionMessage(ExceptionConstant.UPLOAD_FILE_FAILURE);
        } catch (InvalidResponseException e) {
            log.error("无效响应异常", e);
            throw new DemonExceptionMessage(ExceptionConstant.UPLOAD_FILE_FAILURE);
        } catch (IOException e) {
            log.error("IO异常", e);
            throw new DemonExceptionMessage(ExceptionConstant.UPLOAD_FILE_FAILURE);
        } catch (NoSuchAlgorithmException e) {
            log.error("无此算法异常", e);
            throw new DemonExceptionMessage(ExceptionConstant.UPLOAD_FILE_FAILURE);
        } catch (ServerException e) {
            log.error("服务器异常", e);
            throw new DemonExceptionMessage(ExceptionConstant.UPLOAD_FILE_FAILURE);
        } catch (XmlParserException e) {
            log.error("XML解析异常", e);
            throw new DemonExceptionMessage(ExceptionConstant.UPLOAD_FILE_FAILURE);
        } finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                log.error(ExceptionConstant.UPLOAD_FILE_FAILURE, e);
            }
        }
    }

    /**
     * description: 上传文件
     *
     * @param fileInputStream
     * @param fileName
     * @return
     */
    public UploadFileVm upload(InputStream fileInputStream, String fileName) {
        UploadFileVm uploadFileVm = new UploadFileVm();
        String url;
        //获取文件对应扩展名
        String extName = FileNameUtil.extName(fileName);
        //根据扩展名获取对应ContentType 路径，再拼装对应文件名
        //需要修改fileName +
        String getContentType = getContentType(extName);
        extName = "." + extName;
        String uploadFileName = "upload/" + getContentType + PATH_SEPARATOR + DateUtil.format(new Date(), DATE_FORMAT) + PATH_SEPARATOR + UUID.randomUUID().toString().replace("-", "") + PATH_SEPARATOR + fileName;
        url = endpoint + PATH_SEPARATOR + bucketName + PATH_SEPARATOR + uploadFileName;
        try {
            uploadFileVm.setSize(fileInputStream.available());
            minioClient.putObject(PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(uploadFileName)
                    .stream(fileInputStream, fileInputStream.available(), -1)
                    .contentType(getContentType)
                    .build()
            );
        } catch (Exception e) {
            log.error(ExceptionConstant.UPLOAD_FILE_FAILURE, e);
        } finally {
            if (fileInputStream != null) {
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    log.error(ExceptionConstant.UPLOAD_FILE_FAILURE, e);
                }
            }
        }
        uploadFileVm.setFileUrl(url);
        uploadFileVm.setExt(extName);
        uploadFileVm.setName(fileName);
        return uploadFileVm;
    }

    public String getMinioPresignedURL(String fileName) throws IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, ServerException, XmlParserException, InternalException, InsufficientDataException, ErrorResponseException {

        //hutool生成当前日期   yyyyMMdd格式
        SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
        String shortUuid = UUID.randomUUID().toString().replace("-", "");
        String objectName;
        if (StringUtils.isBlank(fileName)) {
            objectName = "files/" + sdf.format(System.currentTimeMillis()) + PATH_SEPARATOR + shortUuid;
        } else {
            objectName = "files/" + sdf.format(System.currentTimeMillis()) + PATH_SEPARATOR + shortUuid + PATH_SEPARATOR + fileName;
        }
        //生成的预签名url可访问的有效时间，最大期限7天
        GetPresignedObjectUrlArgs build = GetPresignedObjectUrlArgs.builder()
                .method(Method.PUT)
                .bucket(bucketName)
                .object(objectName)
                .expiry(60 * 60 * 24)
                .build();
        return minioClient.getPresignedObjectUrl(build);
    }

    public String getContentType(String fileName) {
        // 获取文件扩展名
        String extension = getFileExtension(fileName);

        // 查找并返回 MIME 类型
        return MIME_TYPE_MAP.getOrDefault(extension, "application/octet-stream");
    }

    private String getFileExtension(String fileName) {
        int lastDotIndex = fileName.lastIndexOf('.');
        if (lastDotIndex == -1 || lastDotIndex == fileName.length() - 1) {
            return "";
        }
        return fileName.substring(lastDotIndex + 1).toLowerCase();
    }

    public GetObjectResponse getObject(String fileName) {
        GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(bucketName)
                .object(fileName).build();
        try {
            return minioClient.getObject(objectArgs);
        } catch (Exception e) {
            throw new DemonExceptionMessage(e.getMessage());
        }
    }

    /**
     * 根据路径获取流
     * @param url
     * @return
     */
    public InputStream download(String url) {
        String fileName = url.split(endpoint + PATH_SEPARATOR + bucketName + PATH_SEPARATOR)[1];
        try {
            return minioClient.getObject(
                    GetObjectArgs.builder()
                            .bucket(bucketName)
                            .object(fileName)
                            .build());
        } catch (Exception e) {
            throw new DemonExceptionMessage(e.getMessage());
        }
    }

    /**
     * 上传 XLSX 文件
     */
    public UploadFileVm uploadXlsx(MultipartFile file) {
        UploadFileVm uploadFileVm = new UploadFileVm();

        String url;
        //获取文件对应扩展名
        //String extName = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".")+1);
        //根据扩展名获取对应ContentType 路径，再拼装对应文件名
        //需要修改fileName +
        String getContentType = getContentType("tsv");

        String fileName = file.getOriginalFilename().replace("xlsx", "tsv");
        String uploadFileName = "upload/" + getContentType + PATH_SEPARATOR + DateUtil.format(new Date(), DATE_FORMAT) + PATH_SEPARATOR + UUID.randomUUID().toString().replace("-", "") + PATH_SEPARATOR + fileName;
        url = endpoint + PATH_SEPARATOR + bucketName + PATH_SEPARATOR + uploadFileName;
        InputStream fileInputStream=null;
        try {
            fileInputStream=convertXlsxToTsv(file);
            uploadFileVm.setSize(fileInputStream.available());
            minioClient.putObject(PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(uploadFileName)
                    .stream(fileInputStream, fileInputStream.available(), -1)
                    .contentType(getContentType)
                    .build()
            );
        } catch (Exception e) {
            log.error(ExceptionConstant.UPLOAD_FILE_FAILURE, e);
        } finally {
            if (fileInputStream != null) {
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    log.error(ExceptionConstant.UPLOAD_FILE_FAILURE, e);
                }
            }
        }
        uploadFileVm.setFileUrl(url);
        uploadFileVm.setExt(".tsv");
        uploadFileVm.setName(fileName);
        return uploadFileVm;
    }

    /**
     * 将 XLSX 文件转换为 TSV 格式
     */
    public InputStream convertXlsxToTsv(MultipartFile file) {
        try {
            // 解析 Excel 文件
            InputStream inputStream = file.getInputStream();
            Workbook workbook = new XSSFWorkbook(inputStream);
            Sheet sheet = workbook.getSheetAt(0);

            // 转换为 TSV 文件内容
            StringWriter tsvWriter = new StringWriter();
            for (Row row : sheet) {
                Iterator<Cell> cellIterator = row.iterator();
                while (cellIterator.hasNext()) {
                    Cell cell = cellIterator.next();
                    tsvWriter.append(cell.toString());
                    if (cellIterator.hasNext()) {
                        tsvWriter.append("\t");
                    }
                }
                tsvWriter.append("\n");
            }

            // 将 TSV 数据转换为 InputStream
            return new ByteArrayInputStream(tsvWriter.toString().getBytes());

        } catch (Exception e) {
            throw new DemonExceptionMessage("XLSX 文件转换为 TSV 格式失败");
        }
    }
}
